\documentclass[UTF8]{ctexart}

\usepackage{graphicx}
\usepackage{amsmath}
\usepackage{ctex}
\setlength{\parindent}{2em}

\title{Project:五则运算计算器的实现}

\author{唐瑞良\\ 强基工程力学 3210105852}

\begin{document}
\maketitle
\section{设计思路概要}
整体设计思路如下：
\begin{itemize}
\item[1)]将表达式作为string存储，而后分别拆分其中的数字与运算符至数字数组和运算符数组中。
\item[2)]将数字数组和运算符数组合并为后缀表达式。（核心）
\item[3)]使用栈对后缀表达式进行处理,并得到结果。
\end{itemize}
\section{报错预览}
\begin{itemize}
\item[错误代码1:]表达式缺少数字。例如：$2++$。
\item[错误代码2:]表达式缺少运算符。例如：$(2+3)6$。
\item[错误代码3:]括号不匹配。例如：$2*(1+3))$。
\item[错误代码4:]出现多余的小数点。例如：$2.+5$。
\item[错误代码5:]除数为0。例如：$5/(2-2)$。
\item[错误代码6:]出现非法字符。例如：$2s3$。
\item[错误代码7:]出现空括号。例如：$2()$。
\end{itemize}
\section{表达式的存储}
\indent 这一部分功能通过函数$exp\_input$实现,这是一个$int$类型的函数，返回0说明表达式初步合法，返回错误代码则说明表达式存在逻辑上的错误。程序将表达式的数字与运算符分开存储。首先将表达式作一串$string$保存，而后对每一个字符进行单独判断。一旦检测到不应当在表达式中出现的字符，即返回错误代码6：出现非法字符。\\
\indent 对于数字，引入$integer\_judge$,其值为1说明应作为整数输入，为0则说明进入小数输入，可见其值应当初始化为1,一旦检测到小数点即赋值为0。采用的方法是最简单的$char$转$double$的方法，不多赘述。注意到在一些计算器中，形如$2.+3$和$.2+3$的表达式被视作是合法的，在本程序中认为这些输入是错误的，会返回错误代码4：出现多余小数点。\\
\indent 对于运算符，需要解决在表达式中接续出现的运算符是否合法，程序通过判断在表达式中一个运算符和其下一位的内容是否符合逻辑来实现，如果在表达式中出现不合理的连续运算符，则返回错误代码1：缺少数字;或错误代码7：空括号。连续出现的符号只有"X(",")X","(-"（不包含:")("和"()"）共13种是合理的。另外当表达式的最后一位是运算符时，只有当其为后括号才符合逻辑，程序里给出了特定的判断，同样返回错误代码1。\\
\indent 对于单独出现的负数$(-X)$，程序里记作$(0-X)$,将单目运算符转换为双目运算符。\\
\indent 在函数最后一部分引入了括号匹配性的检查，即对已经存储好的运算符从头至尾遍历一次，出现前括号令$blanket\_count$增加1,遇到后括号则减1。一个正确的表达式最终$blanket\_count$值应为0,并且在检测过程中这个值不应当是负数。一旦这值不为0或某一时刻是负数即令函数返回错误代码3：括号不匹配。\\
\section{后缀表达式的合成}
\indent 经过上一部分，我们得到了存储数字的数组$nums$和存储运算符的数组$ops$。为解决运算符权的问题，又引入了$ops\_level$数组，它和$ops$是一一匹配的，优先级设定如下：0级:$)$，1级:$+$,$-$，2级:$*$,$/$，3级:乘方(缺少字体，就拿乘方两个字代替了），4级:$($。对于括号的定级，体现在出现前括号意味着子表达式的出现，出现后括号意味着子表达式的结束，这说明前括号应当具有最高的优先级，而后括号不应当对其他运算符造成“干扰”（具体体现在实现过程中），故设定其具有最低的优先级。\\
\indent 后缀表达式的存储使用了存储结构体$post\_exp\_unit$的指针的$vector$数组$post\_exp$。对于$post\_exp\_unit$，它包含type,num,op，type值为1说明这结构体存储了一个数字，为0说明它存储了一个运算符。\\
\indent 对于任意表达式，除去括号，都满足数字的个数等于运算符的数目加1，所以程序考虑首先向$post\_exp$中存入一个数字，而后每遇到一个$1-3$级运算符便存入一个数字。也因此，程序中的过程是始终对$ops$数组作操作的，为了解决运算优先级与括号的问题，我引入了$place$数组，它的初始化是仅存储一个0。它存储的内容能够定向到程序所操作的运算符，函数$pl()$起到了将这个数组转化为位置的值的功能，它返回$place$中数字个数和其最后一位数字之和减1，“$place$中的数字个数减1”$=m$代表了目前位于第$m$级子问题中（即第$m$层括号中），每个数字代表了当前该括号内滞留了多少运算符。\\
\indent 完成了前面的铺垫，现在来解释$post()$。首先要说明的是，每当程序向存储后缀表达式的$post\_exp$中推入数字或者字符时，都伴随着这个数字或者字符在$nums$和$ops$中的删除，但是括号不会出现在后缀表达式中，因为并不会对括号作推入操作。程序首先向$post\_exp$中推入了第一个数字，而后进入了循环，它的判断条件是$ops$是否非空。\\
\indent 对于这个循环的第一个if，其主要执行了推入数字的操作。对$ops$中迭代器$pl()$返回值位置的运算符作判断，如果这个运算符的优先级在$1-3$之间，则执行一次推入数字的操作，程序能够保证每次运行到这一语句，对应执行操作的运算符是一个紧接着的新的运算符。如果优先级为4（即前括号），则向$place$中插入一个0，但不删除该括号。如果优先级为0（即后括号），根据优先级的设定，碰到后括号的时候括号里不应该有剩余的运算符了，那么需要做的是对$ops$的$pl()-1$位置进行两次删除，即先删除前括号再删除后括号，而后将$place$的最后一位数字$pop$掉，表示退出这一层括号。事实上，对于一整个括号，可以将其视作数字，那么可以把对括号进行的操作视为一次推入数字的操作，同理的，如果这个“数字”是第一个“数字“，则不需要改变迭代器的位置，如果这个”数字“前面存在运算符，那么需要让迭代器回到前一个位置。在程序中，判断最新的$place$的最后一位是否为0，如果非0，则减一，如果为0,则$continue$,因为来到了一个新的运算符，需要从一开始进行判断。\\
\indent 对于这个循环的第二个if，主要执行了向后缀表达式推入运算符的操作。它判断迭代器位置的运算符和其下一位（如果有下一位）的优先级，如果低于，则说明需要”滞留“这个运算符，而转去对下一个运算符进行操作;另外对于连续出现的乘方运算，考虑到乘方是从右至左的，认为在前的乘方低于在后的乘方的等级，遵从“滞留”原则。如果平级或高于，则需要进入一个循环，将这个运算符和前面”滞留“的运算符一并推入后缀表达式中。对于没有下一位的情况，最后进行类似于平级或高于中所提到的循环。在表达式合理的情况下，上述过程能够将$nums$和$ops$合并成为$post\_exp$。事实上前面对表达式的判断存在漏洞，测试中发现$((2+3)*(4+5))6$这样的表达式通过了前面的检测，不过这样的式子经过$post()$函数的操作后$nums$不能完全清空，于是在$post$的循环之后插入了对$nums$是否为空的判断。否则返回错误代码2：缺少运算符。\\
\section{处理后缀表达式}
\indent 主要操作体现在函数$\_calculator()$中。这里主要使用栈$cal$对后缀表达式进行了处理。如果出现除数为0则返回错误代码5：除数为0。最终$cal$中会剩余一个数字，这个数字就是表达式的运算结果。
\section{算例测试}
\indent 编译example.cpp并运行example得到如下结果（正常使用计算器请编译main.cpp并运行calculator）：
\begin{verbatim}
2++
Error Code 1:Lack of Numbers.
(2+3)6
Error Code 2:Lack of Operators.
2*(1+3))
Error Code 3:Blankets do not compare.
2.+5
Error Code 4:Excess decimal point.
5/(2-2)
Error Code 5:Divided by 0.
2s3
Error Code 6:Illegal characters exist.
2()
Error Code 7:Empty blanket exist.
2^2^3
2^2^3 = 256
(2^2)^3
(2^2)^3 = 64
2^(1+3)-5*(15.23)/(1+2)*3-5
2^(1+3)-5*(15.23)/(1+2)*3-5 = -65.15
1.25+(3*(1+2^2)*3-43)^(4-2)
1.25+(3*(1+2^2)*3-43)^(4-2) = 5.25
2^(1+3))-5*(15.23)/(1+2)*3-5
Error Code 3:Blankets do not compare.
2^(1+3)-5*(15.23)/(1-1)*3-5
Error Code 5:Divided by 0.
\end{verbatim}
\section{程序存在的缺陷与可能的拓展}
\indent 认为程序能够容纳更多双目运算符，包括但不限于组合运算的$P,C$。\\
\indent 程序对于包含多种错误的表达式只能报发现的第一种错误。
















\end{document}
